package com.example.socketio.handler;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.io.IOUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIONamespace;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import com.example.socketio.entity.CanvasRect;
import com.example.socketio.entity.EraseData;
import com.example.socketio.entity.FileDataBase64;
import com.example.socketio.entity.FileResponse;
import com.example.socketio.entity.LoginDto;
import com.example.socketio.entity.Message;
import com.example.socketio.entity.User;
import com.example.socketio.entity.UserLoginRspDto;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;

//事件处理
@Component
@Slf4j
public class SocketIOHandler {
	private static final Logger log = LoggerFactory.getLogger(SocketIOHandler.class);

	@Autowired
	private SocketIOServer server;// 当前服务端对象

	private User user;// 当前用户

	// 把所有连接的客户端都存到这个map里，每个用户名（不可重复）对应各自的客户端
	private static Map<String, SocketIOClient> clientMap = new ConcurrentHashMap<>();

	private static List<User> userList = new ArrayList<User>();

	// 初始化时触发这个方法
	// 服务器启动
	@PostConstruct
	private void autoStartup() {

		if (server == null) {
			log.info("autoStartup .... server is null !");
		} else {
			log.info("autoStartup .... server is not null !");

			// final SocketIONamespace chat1namespace = server.addNamespace("/chat1");

			if (this == null) {
				log.error("this is null");
			} else {
				server.addListeners(this);
				log.info("server.addListeners(this);");
			}
			final SocketIONamespace chat1namespace = server.addNamespace("/chat1");

			server.start();
			System.out.println("服务器启动成功");
		}

	}

	// 当一个bean从spring容器中被销毁前触发这个方法
	// 服务端关闭
	// @PreDestroy注解用于在Bean被销毁之前执行清理操作
	@PreDestroy
	private void autoStop() {

		if (server == null) {
			log.info("autoStop .... server is null !");
		} else {
			log.info("autoStop .... server is not null !");

			server.stop();
			System.out.println("服务器断开连接");
		}

	}

	// 客户端连接的时候触发
	// 监听客户端的connect事件
	// SocketIOClient代表一个客户端连接（客户端的对象），它提供了发送和接收消息的方法。通过此SocketIOClient对象，服务器可以针对该客户端发送消息
	@OnConnect
	public void onConnect(SocketIOClient client) {
		client.sendEvent("connect", "connect ok!");
		log.info("onConnect client="+client);
		client.joinRoom("/chat");
		User thisUser = client.get("user");
		if (thisUser != null) {
			log.info("thisUser duplicate connect , username=" + thisUser.getUsername());
		} else {
			log.info("onConnect user is null");
		}
		log.info("onConnect 已连接客户端:" + client.getSessionId() + " " + client.getHandshakeData().getUrl());
	}

	@OnEvent(value = "login")
	public void onEventLogin(LoginDto loginDto, SocketIOClient client) {
		System.out.println("onlogin客户端发来消息：" + loginDto.getUsername() + " avata=" + loginDto.getAvata());
		// 服务端处理login请求

		var loginedUser = clientMap.get(loginDto.getUsername());
		if (loginedUser == null) {
			User thisUser = new User(loginDto.getUsername(), loginDto.getAvata());
			client.set("user", thisUser);

			clientMap.put(loginDto.getUsername(), client);

			userList.add(thisUser);
			client.sendEvent("loginSuccess", clientMap.keySet().toArray());
			server.getBroadcastOperations().sendEvent("userListChanged", new UserLoginRspDto(userList, thisUser));
//			client.getNamespace().getAllClients().stream().forEach(c->{
//				log.info("client.getNamespace().getAllClients()==>"+c.get("user"));
//			});
		} else {
			client.sendEvent("noneed2login",
					new UserLoginRspDto(userList, new User(loginDto.getUsername(), loginDto.getAvata())));
		}

		log.info("onlogin :: end of login event");

	}

	// 客户端关闭连接时触发
	// 监听客户端的disconnect事件
	@OnDisconnect
	public void onDisconnect(SocketIOClient client) {
		User user = client.get("user");
		if(user == null) {
			return ;
		}
		log.info("onDisconnect user="+user);
		if (clientMap != null) {
			if(user!=null) {
				log.info("clientMap.remove"+user.getUsername());
				clientMap.remove(user.getUsername());
			}
		}
		if (userList != null && user !=null) {
			log.info("userList.remove "+user.getUsername());
			userList.remove(user);
		}
		if (server != null) {
			server.getBroadcastOperations().sendEvent("someoneDisconnect", userList);
		}
		if( user !=null) {
			log.info("onDisconnect 客户端:" +user.getUsername() + "断开连接");
		}else {
			log.info("onDisconnect 客户端: user is null "  + "断开连接");
		}
		
	}

	// 处理前端发送的文字
	@OnEvent(value = "sendMsgToServer")
	public void onEventSendMsgToServer(Message msg, SocketIOClient client) {
//		log.info("server"+server);
//		log.info("client"+client);
		User user = client.get("user");
		if( user ==null ) {
			log.info("client.user is null");
		}else {
			log.info("client.username=="+user.getUsername());	
		}
		
//		log.info("server.getBroadcastOperations.clients.size====="+server.getBroadcastOperations().getClients().size());
//		log.info("on handle sendMsgToServer, then emit broadcaseMsgToClients msg==>"+msg);
		server.getBroadcastOperations().sendEvent("broadcaseMsgToClients",msg);

	}
	
	
	@OnEvent(value = "sendImage")
	public void onEventSendImage(Message msg, SocketIOClient client) {
		server.getBroadcastOperations().sendEvent("broadcaseImageToClients",msg);
	}
	
	
	/**
	 * 处理协同作画
	 * @param obj
	 * @param client
	 */
	@OnEvent(value = "draw")
	public void onEventDraw(CanvasRect obj, SocketIOClient client) {
		
		server.getBroadcastOperations().sendEvent("drawData",obj);
	}
	/**
	 * 处理协同作画，橡皮擦
	 * @param obj
	 * @param client
	 */
	@OnEvent(value = "erase")
	public void onEventErase(EraseData obj, SocketIOClient client) {
		
		server.getBroadcastOperations().sendEvent("eraseData",obj);
	}
	/**
	 * 处理协同作画，全部清除
	 * @param client
	 */
	@OnEvent(value = "clearall")
	public void onEventClearall( SocketIOClient client) {
		
		server.getBroadcastOperations().sendEvent("clearallData");
	}
	
	@OnEvent(value = "showBoard")
	public void onEventShowBoard(Boolean showBoard, SocketIOClient client) {
		
		server.getBroadcastOperations().sendEvent("onShowBoardData",showBoard);
	}
	

	@OnEvent("fileUpload")
	public void onEventFileUpload(FileDataBase64 data, SocketIOClient client) throws JSONException {
		  String filename = data.getFilename();
		  String base64 = data.getBase64();
		  
		  byte[] content = Base64.getDecoder().decode(base64);
		  File file = new File("/Users/shirongxin/tmp/upload/" + filename);
		  try (OutputStream outputStream = new FileOutputStream(file)) {
		    outputStream.write(content);
		  } catch (IOException e) {
		    e.printStackTrace();
		  }
		  
		  FileResponse responseData = new FileResponse();
		  responseData.setFilename(filename); 
		  responseData.setUrl("/uploads/" + filename);
		
		  server.getBroadcastOperations().sendEvent("fileUploaded", responseData);
	}	
	
	
	@OnEvent("fileDownload")
	public void onFileDownload(SocketIOClient client, JSONObject data, AckRequest ackRequest) throws JSONException {
	  String filename = data.getString("filename");
	  File file = new File("/Users/shirongxin/tmp/upload" + filename);
	  
	  if (!file.exists()) {
		  FileDataBase64 responseData = new FileDataBase64();
	    responseData.setError( "File not found");
	    client.sendEvent("fileDownloaded", responseData);
	    return;
	  }
	  
	  byte[] content = null;
	  try (InputStream inputStream = new FileInputStream(file)) {
	    content = IOUtils.toByteArray(inputStream);
	  } catch (IOException e) {
	    e.printStackTrace();
	  }
	  
	  FileDataBase64 responseData = new FileDataBase64();
	  responseData.setFilename(filename);
	  responseData.setBase64( Base64.getEncoder().encodeToString(content));
	  client.sendEvent("fileDownloaded", responseData);
	}
}
